package com.dreamdance.queerball;

import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;

import com.dreamdance.queerball.ChessboardData.DataChangedListener;

import android.content.Context;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Log;
import android.view.MotionEvent;
import net.ds0101.framework.android.neox.action.NXAction;
import net.ds0101.framework.android.neox.action.NXZoom;
import net.ds0101.framework.android.neox.action.NXFadeOut;
import net.ds0101.framework.android.neox.animation.NXAbstractAnimation;
import net.ds0101.framework.android.neox.animation.NXVariantAnimation;
import net.ds0101.framework.android.neox.animation.NXAbstractAnimation.AnimationListener;
import net.ds0101.framework.android.neox.animation.NXAbstractAnimation.State;
import net.ds0101.framework.android.neox.animation.NXPropertyAnimation;
import net.ds0101.framework.android.neox.animation.NXVariantAnimation.VariantListener;
import net.ds0101.framework.android.neox.core.NXNode;
import net.ds0101.framework.android.neox.core.NXNodeGroup;
import net.ds0101.framework.android.neox.core.NXVariant;
import net.ds0101.framework.android.neox.main.NXDirector;
import net.ds0101.framework.android.neox.main.NXScene;
import net.ds0101.framework.android.neox.map.*;
import net.ds0101.framework.android.neox.core.NXVector2f;

import net.ds0101.framework.android.neox.util.LogUtil;
import net.ds0101.framework.android.neox.util.NXEasingCurve;
import net.ds0101.framework.android.neox.util.NXResourceConverter;
import net.ds0101.framework.android.neox.widget.NXBitmapSheet;
import net.ds0101.framework.android.neox.widget.NXLabel;

public class ChessboardUI extends NXNodeGroup implements DataChangedListener {
	private static NXBitmapSheet sHalo;
	private static final short COORDINATE_DIMENSIONALITY = 2;
	private ChessboardData mData;
	private short mSelectedGrid[];
	//private AstarPathFinder mPathFinder;
	private Point mStartPos, mTargetPos;
	private LinkedList<NXVector2f> mPath;
	private NXField2D mField;
	private Paint mPaint = new Paint();
	private RectF mBackRect;
	private int mBallRadius;
	private BallSprite[][] mBallCells = new BallSprite[ChessboardData.GRID_NUM][ChessboardData.GRID_NUM];
	private BallSprite[] mNextBalls = new BallSprite[ChessboardData.NEW_BALLS_NUM];
	private NXAction mDisappear;
	private NXAction mDisplay;
	private int mScore;
	private NXLabel mScoreLabel;
	private QueerBallGameScene mScene;
	private int mGridW, mGridH;
	
	public ChessboardUI(QueerBallGameScene scene, NXBitmapSheet backGroud, ChessboardData data) {
		super();
		if (null != data && null != backGroud) {
			//Log.w("[QueerBall]", "Chessboard size = (" + String.valueOf(rc.width()) + ", " + String.valueOf(rc.height()) + ")");
			mScene = scene;
			addChild(backGroud);
			mBackRect = backGroud.area();
			area(mBackRect);
			mGridW = (int) (mBackRect.width() / ChessboardData.GRID_NUM);
			mGridH = (int) (mBackRect.height() / ChessboardData.GRID_NUM);
			mBallRadius = (int)(mBackRect.width() / ChessboardData.GRID_NUM) >> 1;
			mSelectedGrid = new short [COORDINATE_DIMENSIONALITY];
			mSelectedGrid[0] = mSelectedGrid[1] = -1;
			sHalo = new NXBitmapSheet(NXResourceConverter.getBitmapFromResource(scene, R.drawable.halo), 1, 1);
			sHalo.visible(false);
			addChild(sHalo);
			
			mScoreLabel = new NXLabel("0", 28f, (NXDirector.getScreenWidth() >> 1), 50);
			addChild(mScoreLabel);
			
			mStartPos = new Point();
			mTargetPos = new Point();
			
			for (int i = 0; i < ChessboardData.NEW_BALLS_NUM; ++i) {
				mNextBalls[i] = BallSprite.createBallSprite(scene);
				addChild(mNextBalls[i]);
				mNextBalls[i].position((NXDirector.getScreenWidth() >> 1) + (i - 1) * mNextBalls[i].width(), 
						NXDirector.getScreenHeight() - mNextBalls[i].height());
			}
			
			for (int y = 0; y < ChessboardData.GRID_NUM; ++y) {
				for (int x = 0; x < ChessboardData.GRID_NUM; ++x) {
					mBallCells[y][x] = BallSprite.createBallSprite(scene);
					addChild(mBallCells[y][x]);
					mBallCells[y][x].position(mBackRect.left + x * mBallRadius * 2 + mBallRadius, 
							mBackRect.top + y * mBallRadius * 2 + mBallRadius);
					mBallCells[y][x].visible(false);
				}
			}
			mData = data;
			mField = mData.map();
			
			mDisappear = NXFadeOut.action(200);
			NXAbstractAnimation anim = mDisappear.getAnimation();
			anim.setAnimationListener(new AnimationListener() {
				@Override
				public void onAnimationStart(NXAbstractAnimation animation) {
					// TODO Auto-generated method stub
				}

				@Override
				public void onAnimationFinish(NXAbstractAnimation animation) {
					if (animation instanceof NXPropertyAnimation) {
						for (Enumeration<NXNode> elements = ((NXPropertyAnimation)animation).targetElements();
							elements.hasMoreElements(); ) {
							NXNode elem = elements.nextElement();
							elem.visible(false);
							elem.opacity(255);
						}
						((NXPropertyAnimation) animation).clearTarget();
					}
				}

				@Override
				public void onAnimationRepeat(NXAbstractAnimation animation) {
					// TODO Auto-generated method stub
				}

				@Override
				public void onAnimationStateChanged(
						NXAbstractAnimation animation, State newState,
						State oldState) {
					// TODO Auto-generated method stub
				}
			});
			
			//mDisplay = NXFadeIn.action(200);
			mDisplay = new NXZoom(200, 0f, 1f, NXEasingCurve.Type.InQuad);
			anim = mDisplay.getAnimation();
			anim.setAnimationListener(new AnimationListener() {

				@Override
				public void onAnimationStart(NXAbstractAnimation animation) {
					/*if (animation instanceof NXPropertyAnimation) {
						for (Enumeration<NXNode> elements = ((NXPropertyAnimation)animation).targetElements();
							elements.hasMoreElements(); ) {
							NXNode elem = elements.nextElement();
							elem.opacity(0);
							elem.visible(true);
						}
					}*/
				}

				@Override
				public void onAnimationFinish(NXAbstractAnimation animation) {
					if (animation instanceof NXPropertyAnimation) {
						((NXPropertyAnimation) animation).clearTarget();
					}
				}

				@Override
				public void onAnimationRepeat(NXAbstractAnimation animation) {
					// TODO Auto-generated method stub
					
				}

				@Override
				public void onAnimationStateChanged(
						NXAbstractAnimation animation, State newState,
						State oldState) {
					// TODO Auto-generated method stub
					
				}
			});
		} else {
			throw new NullPointerException();
		}
	}
	
	public void onGameStart() {
		mScore = 0;
		setScore(mScore);
		for (int y = 0; y < ChessboardData.GRID_NUM; ++y) {
			for (int x = 0; x < ChessboardData.GRID_NUM; ++x) {
				mBallCells[y][x].visible(false);
			}
		}
	}
	
	public boolean selectedValid() {
		return (mSelectedGrid[0] >= 0 && mSelectedGrid[1] >= 0
				&& mSelectedGrid[0] < ChessboardData.GRID_NUM && mSelectedGrid[1] < ChessboardData.GRID_NUM);
	}
	
	private void setSelected(int x, int y) {
		mSelectedGrid[0] = (short)x;
		mSelectedGrid[1] = (short)y;
		if (selectedValid()) {
			mBallCells[y][x].playAnimation(true);
		}
	}
	
	@Override
	public boolean onTouch(int action, float x, float y) {
		x -= mBackRect.left;
		y -= mBackRect.top;
		if (MotionEvent.ACTION_UP == action) {
			int col = (int) (x / mGridW);
			int row = (int) (y / mGridH);
			if (col < 0 || col >= ChessboardData.GRID_NUM || row < 0 || row >= ChessboardData.GRID_NUM) {
				/*LogUtil.e("[QueerBall+]", "touch pos = (" + String.valueOf(x) + ", " + String.valueOf(y) + ")");
				LogUtil.e("[QueerBall]", "grid size = (" + String.valueOf(mGridW) + ", " + String.valueOf(mGridH) + ")");
				LogUtil.e("[QueerBall-]", "tap at = (" + String.valueOf(col) + ", " + String.valueOf(row) + ")");*/
			} else {
				tapGrid(col, row);
				mScene.gameOver();
			}
		}
		return true;
	}
	
	public void resetSelected() {
		setSelected(-1, -1); // reset the selected.
	}
	
	private boolean tapGrid(int col, int row) {
		Log.w("[QueerBall]", "into tapGrid");
		if (mData.isFilled(col, row)) {
			setSelected(col, row);
			LogUtil.w("[QueerBall]", "select (" + String.valueOf(col) + ", " + String.valueOf(row) + ")");
			return true;
		} else if (selectedValid()) { // any node was selected.
			mStartPos.set(mSelectedGrid[0], mSelectedGrid[1]);
			mTargetPos.set(col, row);
			//mPath = mPathFinder.searchPath(mStartPos, mTargetPos);
			mPath = NXAStarFinder.find(mField, mStartPos.x, mStartPos.y, mTargetPos.x, mTargetPos.y, false);
			resetSelected();
			LogUtil.w("[QueerBall]", "from (" + String.valueOf(mSelectedGrid[0]) + ", " + String.valueOf(mSelectedGrid[1]) + ")"
					+ "to (" + String.valueOf(col) + ", " + String.valueOf(row) + ")" + 
					(null != mPath ? " can " : " can't ") + "find the path.");
		}
		return false; // it's choose nothing.
	}
	
	private void setScore(int score) {
		mScoreLabel.text(String.valueOf(mScore));
	}
	
	private int calcScore(int clearNum) {
		if (clearNum >= ChessboardData.ELIMINATE_THRESHOLD) {
			return ChessboardData.ELIMINATE_THRESHOLD * 2 + (clearNum - ChessboardData.ELIMINATE_THRESHOLD) * 3;
		}
		return 0;
	}
	
	@Override
	public void draw(Canvas canvas) {
		super.draw(canvas);
		mPaint.setColor(Color.DKGRAY);
		int w = (int)mBackRect.width();
		/*for (int o = 0; o < w; o += (mBallRadius * 2)) {
			canvas.drawLine(mBackRect.left, mBackRect.top + o, mBackRect.right, mBackRect.top + o, mPaint);
			canvas.drawLine(mBackRect.left + o, mBackRect.top, mBackRect.left + o, mBackRect.bottom, mPaint);
		}*/
		if (selectedValid()) {
			sHalo.position(mBallCells[mSelectedGrid[1]][mSelectedGrid[0]].positionX(), 
					mBallCells[mSelectedGrid[1]][mSelectedGrid[0]].positionY());
			sHalo.visible(true);
		} else if (null != mPath) {
			mData.moveBall(mTargetPos.x, mTargetPos.y, mStartPos.x, mStartPos.y);
			sHalo.visible(false);
			mPaint.setColor(Color.GRAY);
			/*for (int i = 0; i < mPath.size(); ++i) {
				canvas.drawCircle(mBackRect.left + width() / ChessboardData.GRID_NUM * mPath.get(i).x() + mBallRadius,
						mBackRect.top + height() / ChessboardData.GRID_NUM * mPath.get(i).y() + mBallRadius, mBallRadius, mPaint);
			}*/
			mPath = null;
			mData.findEliminatable();
			int clearTotal = mData.clearMarkedGrid();
			/** If clear some balls, then don't generate new balls in this turn.*/
			if (0 == clearTotal) {
				mData.putNewBallsToGrid();
				mData.findEliminatable(); 
				clearTotal = mData.clearMarkedGrid(); // new balls may be can cleared.
			}
			if (clearTotal > 0) {
				mScore += calcScore(clearTotal);
				setScore(mScore);
			}
		} else {
			sHalo.visible(false);
		}
	}
	
	@Override
	public void onNewBallPerch(int x, int y, int colorIndex) {
		mBallCells[y][x].changeColor(colorIndex);
		//mBallCells[y][x].opacity(0);
		mBallCells[y][x].scale(0f);
		mBallCells[y][x].visible(true);
		mBallCells[y][x].attachAction(mDisplay);
	}
	
	@Override
	public void onBallClearUp(int x, int y) {
		mBallCells[y][x].attachAction(mDisappear);
	}

	@Override
	public void onMove(int destX, int destY, int srcX, int srcY, int colorIndex) {
		mBallCells[srcY][srcX].visible(false);
		mBallCells[destY][destX].changeColor(colorIndex);
		mBallCells[destY][destX].visible(true);
	}

	@Override
	public void onNewBallsGenerate(int[] colors) {
		if (colors.length >= mNextBalls.length) {
			for (int i = 0; i < mNextBalls.length; ++i) {
				mNextBalls[i].changeColor(colors[i]);
			}
		}
	}

	@Override
	public void onCram() {
		mScene.gameOver();
	}
}
